<script setup>
import { MarkerType, Panel, VueFlow, useVueFlow } from '@vue-flow/core';
import { MiniMap } from '@vue-flow/minimap';
import { defineExpose, nextTick, ref } from 'vue';
import DropzoneBackground from './background/DropzoneBackground.vue';
import Controls from './controls/Controls.vue';
import EdgeControls from './controls/EdgeControls.vue';
import InteractionControls from './controls/InteractionControls.vue';
import useDragAndDrop from './drag/useDnD';
import AnimationEdge from './edge/AnimationEdge.vue';
import CommonLine from './edge/CommonLine.vue';
import ArrowIcon from './icon/ArrowIcon.vue';
import BooleanNode from './node/cmp/BooleanNode.vue';
import ChainNode from './node/cmp/ChainNode.vue';
import CommonNode from './node/cmp/CommonNode.vue';
import ForNode from './node/cmp/ForNode.vue';
import IfNode from './node/cmp/IfNode.vue';
import IteratorNode from './node/cmp/IteratorNode.vue';
import SwitchNode from './node/cmp/SwitchNode.vue';
import WhileNode from './node/cmp/WhileNode.vue';
import AndNode from './node/group/AndNode.vue';
import ContextNode from './node/group/ContextNode.vue';
import NotNode from './node/group/NotNode.vue';
import NoteNode from './node/group/NoteNode.vue';
import OrNode from './node/group/OrNode.vue';
import RouterNode from './node/group/RouterNode.vue';
import SubflowNode from './node/group/SubflowNode.vue';
import SubvarNode from './node/group/SubvarNode.vue';
import ThenNode from './node/group/ThenNode.vue';
import WhenNode from './node/group/WhenNode.vue';
import ScriptNode from './node/script/ScriptNode.vue';

import MonacoEditor from 'monaco-editor-vue3';
import { useLayout } from './debug/useLayout';
import { useRunProcess } from './debug/useRunProcess';
import { useShuffle } from './debug/useShuffle';


const props = defineProps({
  sideBarShow: {
    type: Boolean,
    required: false,
    default: true,
  },
  controlsShow: {
    type: Boolean,
    required: false,
    default: true,
  },
  flowData: {
    type: String,
    required: false,
  },
  showEL: {
    type: Boolean,
    required: false,
    default: false,
  },
})

function guid() {
  return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
    var r = Math.random() * 16 | 0,
      v = c == 'x' ? r : (r & 0x3 | 0x8)
    
    return v.toString(16)
  })
}

const vueFlowKey = ref(guid())
const nodes = ref([])
const edges = ref([])

const edgesUpdatable = ref(true)
const cancelOnError = ref(true)

const shuffle = useShuffle()

const { graph, layout, layout0, previousDirection } = useLayout()

const { run, stop, reset, isRunning, getLog } = useRunProcess({ graph, cancelOnError })

const { 
  fitView, onConnect, addEdges, onPaneReady, setViewport,toObject,fromObject,getNodes,getEdges,
  onEdgeClick,onPaneClick,updateEdgeData,findNode,
  onNodesChange, onEdgesChange
 } = useVueFlow()

const { onDragOver, onDrop, onDragLeave, isDragOver } = useDragAndDrop()

const interactionControlsShow = ref(false)
const processPanelShow = ref(false)

onConnect(connection => {
  connection.animated = true
  connection.id = guid()
  connection.type = 'animation'
  connection.updatable = true
  connection.markerEnd = MarkerType.ArrowClosed
  connection.showToolbar = false
  
  const sourceNode = findNode(connection.source)
  if(sourceNode.type == 'note'){
    connection.data = {type: 'note'}
    connection.label = '注释'
  }else{
    connection.data = {type: 'common'}
    connection.label = ''
  }
  addEdges(connection)
})

//限流方法
function throttle(func, limit) {
    let inThrottle;
    return function() {
        const args = arguments;
        const context = this;
        if (!inThrottle) {
            func.apply(context, args);
            inThrottle = true;
            setTimeout(() => inThrottle = false, limit);
        }
    }
}
// 一秒内调用一次
const controlsRef = ref(null)
const toEL = () => {
  if (controlsRef.value) {
    controlsRef.value.toEL();
  }
}
const throttledFunction = throttle(toEL, 1000);

//节点变更
onNodesChange((changes) => {
  // console.log('onNodesChange',changes)
  throttledFunction();
})

//路径变更
onEdgesChange((changes) => {
  // console.log('onEdgesChange',changes)
  throttledFunction();
})

// async function shuffleGraph() {
//   await stop()

//   reset(getNodes.value)

//   edges.value = shuffle(getEdges.value)

//   nextTick(() => {
//     layoutGraph(previousDirection.value)
//   })
// }

async function layoutGraph(direction) {
  await stop()

  const flowData = toObject()
  nodes.value = flowData.nodes
  edges.value = flowData.edges

  reset(nodes.value)
  nodes.value = layout(nodes.value, edges.value, direction)
  vueFlowKey.value++;
  // nodes.value = layout(getNodes.value, getEdges.value, direction)
}

const requestId = ref('')
async function runFlow() {
  await stop()
  nextTick(()=>{
    const flowData = toObject()
    nodes.value = flowData.nodes
    edges.value = flowData.edges
    vueFlowKey.value++;
    reset(nodes.value)
    requestId.value = getId()
    run(nodes.value,requestId.value)
  })
}

const codeOptions = ref({
  colorDecorators: true,
  lineHeight: 24,
  tabSize: 2,
})
const popupLog = ref(false)
const logStr = ref('')
const runLog = async () => {
  logStr.value = await getLog(requestId.value)
  popupLog.value = true
}

function getId() {
  return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
    var r = Math.random() * 16 | 0,
        v = c == 'x' ? r : (r & 0x3 | 0x8);
    return v.toString(16);
  });
}

onPaneReady(({ fitView }) => {
  setViewport({ x: 0, y: 0, zoom: 1 })
})

onMounted(() => {
  if(props.flowData){
    const flowDataStr = JSON.parse(props.flowData)
    if (flowDataStr) {
      fromObject(flowDataStr)
    }
  }

  if(props.showEL){
    toEL()
  }
})

// 当前选中路径
const edgeControlsKey = ref(0)
const currEdge = ref({})
const edgeControlsShow = ref(false)


// 点击连线
onEdgeClick((event, edge) => {
  currEdge.value = event.edge
  edgeControlsOpen()
})

// 路径设置面板隐藏
const edgeControlsClose = () => {
  edgeControlsShow.value = false
  currEdge.value.selected = false
  currEdge.value.showToolbar = false
  currEdge.value = {}

  // updateEdgeData(currEdge.value.id, {showToolbar: false})
}

// 路径设置面板显示
const edgeControlsOpen = edge => {
  if(edge){
    currEdge.value = edge
  }
  currEdge.value.selected = true
  currEdge.value.showToolbar = true
  edgeControlsKey.value = currEdge.value.id
  edgeControlsShow.value = true

  // updateEdgeData(currEdge.value.id, {showToolbar: true})
}

// 点击画板
onPaneClick(e=>{
  edgeControlsClose();
  throttledFunction();
})

//清空画布
const removeNodesEdges = () => {
  nodes.value = []
  edges.value = []
}

//调试模式
const debugMode = (data) => {
  processPanelShow.value = data.processPanelShow
  if(processPanelShow.value){
    // reset(getNodes.value)
    // nodes.value = getNodes.value
    // edges.value = getEdges.value
    // layout0(getNodes.value, getEdges.value)

    const flowData = toObject()
    nodes.value = flowData.nodes
    edges.value = flowData.edges
    reset(nodes.value)
    layout0(nodes.value, edges.value)
    vueFlowKey.value++;
  }
}


// 提供调用方法
defineExpose({
  toEL,
})
</script>

<template>
  <div class="layout-flow dndflow" @drop="onDrop">
    <vs-row style="block-size: 100%;">
      <!-- <vs-col :sm="2">
        <vs-tabs alignment="fixed">
          <vs-tab label="流程">
            <Sidebar />
          </vs-tab>
          <vs-tab label="文档">
            <DocumentBar/>
          </vs-tab>
        </vs-tabs>
      </vs-col> -->
      <vs-col :sm="12">
        <VueFlow 
          class="custom-node-flow interactionflow"
          :nodes="nodes" 
          :edges="edges" 
          :key="vueFlowKey"
          :default-viewport="{ zoom: 1 }"
          :min-zoom="0.1"
          :max-zoom="3"
          :connection-radius="30"
          auto-connect
          @dragover="onDragOver"
          @dragleave="onDragLeave"
          :edges-updatable="edgesUpdatable"
        >

          <template #node-common="props"> <CommonNode :id="props.id" :data="props.data" :source-position="props.sourcePosition" :target-position="props.targetPosition" @layoutGraph="layoutGraph" @toEL="toEL" /> </template>
          <template #node-switch="props"> <SwitchNode :id="props.id" :data="props.data" :source-position="props.sourcePosition" :target-position="props.targetPosition" @layoutGraph="layoutGraph" @toEL="toEL" /> </template>
          <template #node-if="props"> <IfNode :id="props.id" :data="props.data" :source-position="props.sourcePosition" :target-position="props.targetPosition" @layoutGraph="layoutGraph" @toEL="toEL" /> </template>
          <template #node-while="props"> <WhileNode :id="props.id" :data="props.data" :source-position="props.sourcePosition" :target-position="props.targetPosition" @layoutGraph="layoutGraph" @toEL="toEL" /> </template>
          <template #node-for="props"> <ForNode :id="props.id" :data="props.data" :source-position="props.sourcePosition" :target-position="props.targetPosition" @layoutGraph="layoutGraph" @toEL="toEL" /> </template>
          <template #node-iterator="props"> <IteratorNode :id="props.id" :data="props.data" :source-position="props.sourcePosition" :target-position="props.targetPosition" @layoutGraph="layoutGraph" @toEL="toEL" /> </template>
          <template #node-chain="props"> <ChainNode :id="props.id" :data="props.data" :source-position="props.sourcePosition" :target-position="props.targetPosition" @layoutGraph="layoutGraph" @toEL="toEL" /> </template>
          
          <template #node-script="props"> <ScriptNode :id="props.id" :data="props.data" :source-position="props.sourcePosition" :target-position="props.targetPosition" @layoutGraph="layoutGraph" @toEL="toEL" /> </template>
          
          <template #node-boolean="props"> <BooleanNode :id="props.id" :data="props.data" :source-position="props.sourcePosition" :target-position="props.targetPosition" @layoutGraph="layoutGraph" @toEL="toEL" /> </template>
          <template #node-and="props"> <AndNode :id="props.id" :data="props.data" :source-position="props.sourcePosition" :target-position="props.targetPosition" @layoutGraph="layoutGraph" @toEL="toEL" /> </template>
          <template #node-or="props"> <OrNode :id="props.id" :data="props.data" :source-position="props.sourcePosition" :target-position="props.targetPosition" @layoutGraph="layoutGraph" @toEL="toEL" /> </template>
          <template #node-not="props"> <NotNode :id="props.id" :data="props.data" :source-position="props.sourcePosition" :target-position="props.targetPosition" @layoutGraph="layoutGraph" @toEL="toEL" /> </template>
          
          <template #node-when="props"> <WhenNode :id="props.id" :data="props.data" :source-position="props.sourcePosition" :target-position="props.targetPosition" @layoutGraph="layoutGraph" @toEL="toEL" /> </template>
          <template #node-then="props"> <ThenNode :id="props.id" :data="props.data" :source-position="props.sourcePosition" :target-position="props.targetPosition" @layoutGraph="layoutGraph" @toEL="toEL" /> </template>
          <template #node-subvar="props"> <SubvarNode :id="props.id" :data="props.data" :source-position="props.sourcePosition" :target-position="props.targetPosition" @layoutGraph="layoutGraph" @toEL="toEL" /> </template>
          <template #node-subflow="props"> <SubflowNode :id="props.id" :data="props.data" :source-position="props.sourcePosition" :target-position="props.targetPosition" @layoutGraph="layoutGraph" @toEL="toEL" /> </template>
          <template #node-context="props"> <ContextNode :id="props.id" :data="props.data" :source-position="props.sourcePosition" :target-position="props.targetPosition" @layoutGraph="layoutGraph" @toEL="toEL" /> </template>
          <template #node-router="props"> <RouterNode :id="props.id" :data="props.data" :source-position="props.sourcePosition" :target-position="props.targetPosition" @layoutGraph="layoutGraph" @toEL="toEL" /> </template>
          <template #node-note="props"> <NoteNode :id="props.id" :data="props.data" :source-position="props.sourcePosition" :target-position="props.targetPosition" @layoutGraph="layoutGraph" @toEL="toEL" /> </template>

          <template #edge-animation="edgeProps">
            <AnimationEdge
              :id="edgeProps.id"
              :source="edgeProps.source"
              :target="edgeProps.target"
              :source-x="edgeProps.sourceX"
              :source-y="edgeProps.sourceY"
              :targetX="edgeProps.targetX"
              :targetY="edgeProps.targetY"
              :source-position="edgeProps.sourcePosition"
              :target-position="edgeProps.targetPosition"
              :data="edgeProps.data"
              :markerEnd="edgeProps.markerEnd"
              :style="edgeProps.style"
              :showToolbar="edgeProps.showToolbar"
              @edge-controls-open="edgeControlsOpen"
              @edge-controls-close="edgeControlsClose"
              @layoutGraph="layoutGraph"
            />
          </template>

          <template #connection-line="{ sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition }">
            <CommonLine
              :source-x="sourceX"
              :source-y="sourceY"
              :target-x="targetX"
              :target-y="targetY"
              :source-position="sourcePosition"
              :target-position="targetPosition"
            />
          </template>

          <DropzoneBackground
            :style="{
              backgroundColor: isDragOver ? '#e7f3ff' : 'transparent',
              transition: 'background-color 0.2s ease',
            }"
          >
            <p v-if="isDragOver">Drop here</p>
          </DropzoneBackground>

          <InteractionControls v-show="interactionControlsShow" />
          <EdgeControls
            v-if="edgeControlsShow"
            :key="edgeControlsKey"
            :edge="currEdge"
            @edge-controls-close="edgeControlsClose"
          />

          <Controls 
            v-if="controlsShow"
            ref="controlsRef"
            :processPanelShow="processPanelShow"
            @update-interaction-controls="(val) => interactionControlsShow = val" 
            @removeNodesEdges="removeNodesEdges"
            @debugMode="debugMode"
            @layoutGraph="layoutGraph"
          />

          <MiniMap pannable zoomable> </MiniMap>

          <Panel v-if="processPanelShow" class="process-panel" position="top-center">
            <div class="layout-panel">
              <button v-if="isRunning" class="stop-btn" title="停止" @click="stop">
                <ArrowIcon name="stop" />
                <span class="spinner" />
              </button>
              <button v-else title="开始" @click="runFlow()">
                <ArrowIcon name="play" />
              </button>

              <button title="LR布局" @click="layoutGraph('LR')">
                <ArrowIcon name="arrowRight" />
              </button>

              <button title="TB布局" @click="layoutGraph('TB')">
                <ArrowIcon name="arrowDown" />
              </button>

              <button title="RL布局" @click="layoutGraph('RL')">
                <ArrowIcon name="arrowLeft" />
              </button>

              <button title="BT布局" @click="layoutGraph('BT')">
                <ArrowIcon name="arrowUp" />
              </button>

              <!-- <button title="重置布局" @click="shuffleGraph">
                <ArrowIcon name="shuffle" />
              </button> -->

              <button title="执行日志" @click="runLog()">
                <ArrowIcon name="log" />
              </button>
            </div>

            <div class="checkbox-panel">
              <input v-model="cancelOnError" type="checkbox" />
              <label>出现错误时取消</label>
            </div>
          </Panel>
        </VueFlow>
      </vs-col>
    </vs-row>
    
    <vs-dialog class="ivy-log-dialog" v-model="popupLog" auto-width full-screen overlay-blur shape="square">
      <template #header>
        <h3>运行日志</h3>
      </template>
      <div class="con-content-scroll" style="block-size: 90vh">
        <MonacoEditor
          style="block-size: 100%;  inline-size: 100%;"
          theme="vs"
          :options="codeOptions"
          language="java"
          v-model:value="logStr"
        />
      </div>
    </vs-dialog>
  </div>
</template>

<style>
@import '@vue-flow/core/dist/style.css';
@import "@vue-flow/core/dist/theme-default.css";
@import "@vue-flow/controls/dist/style.css";
@import "@vue-flow/minimap/dist/style.css";
@import "@vue-flow/node-resizer/dist/style.css";


.vue-flow__minimap {
  transform: scale(75%);
  transform-origin: bottom right;
}

.layout-flow {
  block-size: 100%;
  inline-size: 100%;
}

.process-panel,.common-panel,
.layout-panel {
  display: flex;
  gap: 10px;
}

.process-panel,.common-panel {
  display: flex;
  flex-direction: column;
  padding: 10px;
  border-radius: 8px;
  background-color: #2d3748;
  box-shadow: 0 0 10px rgba(0, 0, 0, 50%);
  inline-size: min-content;
  inset-inline-end: 36px !important;
}

.process-panel button,.common-panel button {
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 8px;
  background-color: #4a5568;
  block-size: 40px;
  box-shadow: 0 0 10px rgba(0, 0, 0, 50%);
  color: white;
  cursor: pointer;
  font-size: 16px;
  inline-size: 40px;
}


.process-panel button:hover,.common-panel button:hover,
.layout-panel button:hover
{
  background-color: #2563eb;
  transition: background-color 0.2s;
}

.process-panel label,.common-panel label {
  color: white;
  font-size: 12px;
}

.checkbox-panel {
  display: flex;
  align-items: center;
  gap: 10px;
}

.stop-btn svg {
  display: none;
}

.stop-btn:hover svg {
  display: block;
}

.stop-btn:hover .spinner {
  display: none;
}

.layout-wrapper-ivy{
  block-size: 100%;
}

.spinner {
  border: 3px solid #f3f3f3;
  border-radius: 50%;
  animation: spin 1s linear infinite;
  block-size: 10px;
  border-block-start: 3px solid #2563eb;
  inline-size: 10px;
}

.spinner2 {
  animation: spin 1s linear infinite;
  block-size: 10px;
  font-size: 16px;
  inline-size: 10px;
}

@keyframes spin {
  0% {
    transform: rotate(0deg);
  }

  100% {
    transform: rotate(360deg);
  }
}


.dnd-flow {
  display:flex;
  flex-direction:column;
  block-size:100%;
  block-size: calc(100vh - 11rem);
  inline-size: 100%;
}

.dnd-flow aside {
  background:#10b981bf;
  border-inline-end:1px solid #eee;
  box-shadow:0 5px 10px 0 rgba(0,0,0,30%);
  box-shadow:0 5px 10px #0000004d;
  color:#fff;
  font-size:12px;
  font-weight:700;
  padding-block:15px;
  padding-inline:10px
}

.dnd-flow aside .nodes>* {
  box-shadow:5px 5px 10px 2px rgba(0,0,0,25%);
  box-shadow:5px 5px 10px 2px #00000040;
  cursor:grab;
  font-weight:500;
  margin-block-end:10px
}

.dnd-flow aside .description {
  margin-block-end:10px
}

.dnd-flow .vue-flow-wrapper {
  flex-grow:1;
  block-size:100%
}

@media screen and (min-width: 640px) {
  .dnd-flow {
    flex-direction:row
  }

  .dnd-flow aside {
    min-inline-size:25%
  }


}

@media screen and (max-width: 639px) {
  .dnd-flow aside .nodes {
    display:flex;
    flex-direction:row;
    gap:5px
  }


}

.dropzone-background {
  position:relative;
  block-size:100%;
  inline-size:100%
}

.dropzone-background .overlay {
  position:absolute;
  z-index:1;
  display:flex;
  align-items:center;
  justify-content:center;
  block-size:100%;
  inline-size:100%;
  inset-block-start:0;
  inset-inline-start:0;
  pointer-events:none
}
</style>
